const markdown_table = require("markdown-table");
const xml2js = require("xml-js");
const xmldom = require("xmldom");

const { json_compacted } = require("./transform_tool");
var json_stringify = require("json-stable-stringify");
const parse_markdown_table = require("parse-markdown-table");

const resolveCode = (_doc, node) => eval(`(${node.strValue})`);

const yamlOptions = {
    customTags: [
        { tag: "!fn", class: Function, resolve: resolveCode },
        { tag: "!re", class: RegExp, resolve: resolveCode },
    ],
};

async function parse(content, type, options) {
    switch (type?.toLowerCase()) {
        case "markdown_table_array": {
            const result = [];
            const table =
                await parse_markdown_table.createMarkdownArrayTable(content);
            result.push(table.headers);
            for await (const row of table.rows) {
                if (!row[0].replace(/-/g, "").trim()) {
                    continue;
                }
                result.push(row);
            }
            return result;
        }
        case "markdown_table":
        case "markdown_table_object": {
            const result = [];
            const table =
                await parse_markdown_table.createMarkdownObjectTable(content);
            for await (const row of table) {
                const values = Object.values(row);
                if (!values[0].replace(/-/g, "").trim()) {
                    continue;
                }
                result.push(row);
            }
            return result;
        }
        default: {
            return parse_sync(content, type, options);
        }
    }
}

function parse_sync(content, type, options) {
    switch (type?.toLowerCase()) {
        case "yml":
        case "yaml": {
            const opts = Object.assign({}, yamlOptions, { merge: true });
            return require("yaml").parse(content, opts);
        }
        case "json": {
            if (options?.strip_comments) {
                content = require("strip-json-comments")(content);
            }
            return JSON.parse(content);
        }
        case "xml": {
            return new xmldom.DOMParser().parseFromString(content);
        }
        case "xml2json": {
            return xml2js.xml2json(content);
        }
        case "xml2js": {
            return xml2js.xml2js(content);
        }
        case "json2xml": {
            return xml2js.json2xml(content);
        }
        case "js2xml": {
            return xml2js.js2xml(content);
        }
        case "lua_assignment": {
            const ast = require("luaparse").parse(content, {
                locations: true,
                comments: true,
            });
            const ret = {};
            for (let i = 0, len = ast.body.length; i < len; ++i) {
                const statement = ast.body[i];
                if (statement.type === "AssignmentStatement") {
                    ret[statement.variables[0].name] = eval(
                        statement.init[0].raw,
                    );
                }
            }
            return ret;
        }
        case "lua": {
            const ast = require("luaparse").parse(content, {
                locations: true,
                comments: true,
            });
            const all = ast.body.concat(ast.comments).sort((s1, s2) => {
                return (
                    s1.loc.start.line - s2.loc.start.line ||
                    s1.loc.start.column - s2.loc.start.column
                );
            });
            return all;
        }
        case "svn_diff": {
            const ret = require("./svn_diff/diff_parser").parse(content);
            return ret;
        }
        case "sequence": {
            return content
                .split(/[^0-9-]+/)
                .flatMap((v) => {
                    const arr = v.split("-");
                    if (arr.length === 2) {
                        const ret = [];
                        for (let i = +arr[0]; i <= +arr[1]; ++i) {
                            ret.push(`${i}`);
                        }
                        return ret;
                    }
                    return v;
                })
                .filter((v) => v.trim())
                .map((v) => +v)
                .sort((a, b) => a - b);
        }
    }
}

async function stringify(data, type, options) {
    switch (type?.toLowerCase()) {
        default: {
            return stringify_sync(data, type, options);
        }
    }
}

function stringify_sync(data, type, options) {
    switch (type?.toLowerCase()) {
        case "yml":
        case "yaml": {
            const opts = Object.assign({}, yamlOptions, options);
            return require("yaml").stringify(data, opts);
        }
        case "yml_compacted":
        case "yaml_compacted": {
            return require("yamljs").stringify(
                data,
                options.inline === undefined ? 1 : options.inline,
                options.space === undefined ? 2 : options.space,
            );
        }
        case "json": {
            options = Object.assign({ space: 4 }, {}, options);
            let defaultSortKeys = [];
            defaultSortKeys = Object.assign(defaultSortKeys, options.sort_keys);
            return json_stringify(data, {
                space: options.indent != null ? options.indent : 4,
                cmp: (a, b) => {
                    const idxa = defaultSortKeys.indexOf(a.key);
                    const idxb = defaultSortKeys.indexOf(b.key);
                    if (idxa !== idxb) {
                        if (idxa === -1) {
                            return 1;
                        } else {
                            return -1;
                        }
                        return idxa - idxb;
                    }
                    const keya = parseInt(a.key, 10);
                    const keyb = parseInt(b.key, 10);
                    return keya - keyb;
                },
            });
        }
        case "json_compacted": {
            options = Object.assign({ space: 4 }, options);
            return json_compacted(data, options?.space, options?.sort_keys);
        }
        case "json_simple": {
            options = Object.assign({ space: 4 }, options);
            return JSON.stringify(data, null, options.space);
        }
        case "markdown_table": {
            const records = [];
            let headers;
            for (const k in data) {
                if (Object.hasOwn(data, k)) {
                    const ele = data[k];
                    if (!headers) {
                        headers = options?.headers || Object.keys(ele);
                        records.push(headers);
                    }
                    records.push(headers.map((header) => ele[header]));
                }
            }
            return markdown_table(records, options);
        }
        case "string": {
            return data?.toString();
        }
        case "sequence": {
            let str = "";
            const delimiter = ",",
                joint = "-";
            let in_sequence = false;
            let prev = null;
            data.sort((a, b) => a - b).forEach((curr, curr_index, arr) => {
                if (prev == null) {
                    str += curr;
                } else if (prev !== curr - 1) {
                    if (in_sequence) {
                        str += prev;
                        in_sequence = false;
                    }
                    str += delimiter + curr;
                } else if (!in_sequence) {
                    str += joint;
                    in_sequence = true;
                }
                if (curr_index === arr.length - 1 && in_sequence) {
                    str += curr;
                }
                prev = curr;
            });
            return str;
        }
        default: {
            // 默认返回自身
            return data;
        }
    }
}

module.exports = {
    parse,
    parse_sync,
    stringify,
    stringify_sync,
};
